/*
 *    Copyright 2021 Huawei Technologies Co., Ltd.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.edgegallery.developer.service.application.action.impl.vm;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.edgegallery.developer.model.application.Application;
import org.edgegallery.developer.model.application.vm.EnumVMStatus;
import org.edgegallery.developer.model.application.vm.VirtualMachine;
import org.edgegallery.developer.model.filesystem.FileSystemResponse;
import org.edgegallery.developer.model.instantiate.vm.EnumImageExportStatus;
import org.edgegallery.developer.model.instantiate.vm.ImageExportInfo;
import org.edgegallery.developer.model.lcm.LcmLog;
import org.edgegallery.developer.model.meao.ThirdSystem;
import org.edgegallery.developer.model.operation.ActionStatus;
import org.edgegallery.developer.model.operation.EnumActionStatus;
import org.edgegallery.developer.model.operation.EnumOperationObjectType;
import org.edgegallery.developer.model.resource.mephost.MepHost;
import org.edgegallery.developer.model.resource.vm.EnumVmImageSlimStatus;
import org.edgegallery.developer.model.resource.vm.EnumVmImageStatus;
import org.edgegallery.developer.model.resource.vm.VMImage;
import org.edgegallery.developer.service.application.ApplicationService;
import org.edgegallery.developer.service.application.action.impl.AbstractAction;
import org.edgegallery.developer.service.application.common.IContextParameter;
import org.edgegallery.developer.service.application.impl.vm.VMAppOperationServiceImpl;
import org.edgegallery.developer.service.application.vm.VMAppVmService;
import org.edgegallery.developer.service.recource.mephost.MepHostService;
import org.edgegallery.developer.service.recource.vm.VMImageService;
import org.edgegallery.developer.util.BusinessConfigUtil;
import org.edgegallery.developer.util.HttpClientUtil;
import org.edgegallery.developer.util.InitConfigUtil;
import org.edgegallery.developer.util.SpringContextUtil;
import org.edgegallery.developer.util.UploadFileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

public class DownloadImageAction extends AbstractAction {

    public static final Logger LOGGER = LoggerFactory.getLogger(DownloadImageAction.class);

    public static final String ACTION_NAME = "Download Image";

    // time out: 120 min.
    public static final int TIMEOUT = 120 * 60 * 1000;

    //interval of the query, 20s.
    public static final int INTERVAL = 20000;

    VMAppOperationServiceImpl vmAppOperationService = (VMAppOperationServiceImpl) SpringContextUtil
        .getBean(VMAppOperationServiceImpl.class);

    VMImageService vmImageService = (VMImageService) SpringContextUtil.getBean(VMImageService.class);

    VMAppVmService vmAppVmService = (VMAppVmService) SpringContextUtil.getBean(VMAppVmService.class);

    ApplicationService applicationService = (ApplicationService) SpringContextUtil.getBean(ApplicationService.class);

    MepHostService mepHostService = (MepHostService) SpringContextUtil.getBean(MepHostService.class);

    UploadFileUtil uploadFileUtil = (UploadFileUtil) SpringContextUtil.getBean(UploadFileUtil.class);

    @Override
    public String getActionName() {
        return ACTION_NAME;
    }

    @Override
    public boolean execute() {
        //Start action , save action status.
        String vmId = (String) getContext().getParameter(IContextParameter.PARAM_VM_ID);
        String applicationId = (String) getContext().getParameter(IContextParameter.PARAM_APPLICATION_ID);
        Application application = applicationService.getApplication(applicationId);
        String statusLog = "Start to download vm image for vm Id：" + vmId;
        LOGGER.info(statusLog);
        ActionStatus actionStatus = initActionStatus(EnumOperationObjectType.VM_IMAGE_INSTANCE, vmId, ACTION_NAME,
            statusLog);
        //create image.
        updateActionProgress(actionStatus, 30, "start to query image info");

        String mepHostId = application.getMepHostId();
        if (null == mepHostId || "".equals(mepHostId)) {
            actionStatus.setStatus(EnumActionStatus.FAILED);
            updateActionError(actionStatus, "Sandbox not selected. Failed to download vm image");
            return false;
        }
        MepHost mepHost = mepHostService.getHost(mepHostId);

        List<ThirdSystem> thirdSystems = HttpClientUtil.getThirdSystems();
        LOGGER.info("thirdSystems:{}", thirdSystems);
        boolean ret;
        if (!CollectionUtils.isEmpty(thirdSystems)) {
            ret = saveMeaoImageToImageMgmt(actionStatus, mepHost, thirdSystems);
        } else {
            ret = saveLcmImageToImageMgmt(actionStatus);
        }
        return ret;
    }

    private boolean saveLcmImageToImageMgmt(ActionStatus actionStatus) {
        LcmLog lcmLog = new LcmLog();
        boolean result = queryImageInfoFromFileSystem();
        if (!result) {
            String msg = "query vm  image info from fileSystem failed. The log is : " + lcmLog.getLog();
            updateActionError(actionStatus, msg);
            modifyImageExportInfo(EnumImageExportStatus.FAILED, msg);
        }
        String msg = "query vm  image info from fileSystem success";
        updateActionProgress(actionStatus, 60, msg);
        modifyImageExportInfo(EnumImageExportStatus.SUCCESS, msg);
        // save to image mgmt
        boolean saveResult = saveImageToImageMgmt();
        if (!saveResult) {
            updateActionError(actionStatus, "save vm  image info to imageMgmt fail.");
        }
        updateActionProgress(actionStatus, 100, "download image success");
        return true;
    }

    private boolean saveMeaoImageToImageMgmt(ActionStatus actionStatus, MepHost mepHost,
        List<ThirdSystem> thirdSystems) {
        ThirdSystem thirdSystem = thirdSystems.get(0);
        String url = thirdSystem.getUrl();
        LOGGER.info("MEAO url:{}", url);
        if (StringUtils.isBlank(url) || !HttpClientUtil.checkUrl(url)) {
            String msg = "download vm  image  failed. the url of selected MEAO system is invalid";
            updateActionError(actionStatus, msg);
            return false;
        }
        String imageId = (String) getContext().getParameter(IContextParameter.PARAM_IMAGE_INSTANCE_ID);
        if (StringUtils.isBlank(imageId)) {
            updateActionError(actionStatus, "download vm  image  failed. created imageId is empty");
            return false;
        }
        //download and upload image to filesystem
        String vmId = (String) getContext().getParameter(IContextParameter.PARAM_VM_ID);
        ImageExportInfo imageExportInfo = vmAppOperationService.getImageExportInfo(vmId);
        byte[] fileBytes = HttpClientUtil.downloadMeaoImage(imageExportInfo.getDownloadUrl());
        if (fileBytes.length == 0) {
            LOGGER.error("download vm image res is null!");
            updateActionError(actionStatus, "download vm image res is null!");
            return false;
        }
        // to file and store & invoke filesystem interface to upload image
        String imagePathStr = getUploadVmImageRootDir(10000);
        File imageParentPath = new File(imagePathStr);
        if (!imageParentPath.exists()) {
            imageParentPath.mkdirs();
        }
        LOGGER.info("imageName:{}", imageExportInfo.getName() + "." + imageExportInfo.getFormat());
        File imageFile = new File(imageParentPath + imageExportInfo.getName() + "." + imageExportInfo.getFormat());
        String fileSystemImageId = "";
        try {
            FileUtils.writeByteArrayToFile(imageFile, fileBytes);
            fileSystemImageId = uploadFileUtil.uploadFile(imageFile.getCanonicalPath());
        } catch (IOException e) {
            LOGGER.error("write downloaded byte arr to file failed {}", e.getMessage());
            return false;
        }
        LOGGER.info("fileSystemImageId:{}", fileSystemImageId);
        if (StringUtils.isBlank(fileSystemImageId)) {
            LOGGER.error("upload file to filesystem result imageId is null");
            return false;
        }
        //to change imageExportInfo and save info to vm_image
        LOGGER.info("fileSystemAddress:{}", uploadFileUtil.getFileSystemAddress());
        String downloadUrl = uploadFileUtil.getFileSystemAddress() + "/image-management/v1/images/" + fileSystemImageId;
        boolean res = saveImageUrl(imageExportInfo, downloadUrl);
        if (!res) {
            LOGGER.error("save image url occur error!");
            return false;
        }
        boolean updateRes = updateExportInfo(imageExportInfo);
        if (!updateRes) {
            LOGGER.error("update image export other info occur error!");
            return false;
        }

        boolean saveResult = saveImageToImageMgmt();
        if (!saveResult) {
            updateActionError(actionStatus, "save vm  image info to imageMgmt fail.");
            return false;
        }
        modifyImageExportInfo(EnumImageExportStatus.SUCCESS, "download image success");
        updateActionProgress(actionStatus, 100, "download image success");
        return true;
    }

    private boolean updateExportInfo(ImageExportInfo imageExportInfo) {
        String vmId = (String) getContext().getParameter(IContextParameter.PARAM_VM_ID);
        FileSystemResponse imageResult = HttpClientUtil.queryImageCheck(imageExportInfo.getDownloadUrl());
        String checkSum = imageResult.getCheckStatusResponse().getCheckInfo().getChecksum();
        if (!StringUtils.isEmpty(checkSum)) {
            imageExportInfo.setCheckSum(checkSum);
            imageExportInfo.setImageFileName(imageResult.getFileName());
            imageExportInfo.setStatus(EnumImageExportStatus.SUCCESS);
            imageExportInfo.setFormat(imageResult.getCheckStatusResponse().getCheckInfo().getImageInfo().getFormat());
            imageExportInfo
                .setImageSize(imageResult.getCheckStatusResponse().getCheckInfo().getImageInfo().getImageSize());
            imageExportInfo.setDownloadUrl(imageExportInfo.getDownloadUrl() + "/action/download");
            vmAppOperationService.modifyExportInfo(vmId, imageExportInfo);
            return true;
        }
        LOGGER.error("checkSum is null!");
        return false;
    }

    private boolean saveImageUrl(ImageExportInfo imageExportInfo, String url) {
        String vmId = (String) getContext().getParameter(IContextParameter.PARAM_VM_ID);
        imageExportInfo.setDownloadUrl(url);
        return vmAppOperationService.modifyExportInfo(vmId, imageExportInfo);
    }

    private String getUploadVmImageRootDir(int imageId) {
        return InitConfigUtil.getWorkSpaceBaseDir() + BusinessConfigUtil.getTmpPath() + "SystemImage" + File.separator
            + imageId + File.separator + "image" + File.separator;
    }

    private boolean saveImageToImageMgmt() {
        String applicationId = (String) getContext().getParameter(IContextParameter.PARAM_APPLICATION_ID);
        String vmId = (String) getContext().getParameter(IContextParameter.PARAM_VM_ID);
        VirtualMachine vm = vmAppVmService.getVm(applicationId, vmId);
        VMImage vmImage = vmImageService.getVmImageById(vm.getImageId());
        SimpleDateFormat date = new SimpleDateFormat("yyyyMMddHHmm");
        vmImage.setName(vm.getImageExportInfo().getName() + "-" + date.format(new Date()));
        vmImage.setDownLoadUrl(vm.getImageExportInfo().getDownloadUrl());
        vmImage.setFileMd5(vm.getImageExportInfo().getCheckSum());
        vmImage.setImageFileName(vm.getImageExportInfo().getImageFileName());
        vmImage.setImageSize(Long.valueOf(vm.getImageExportInfo().getImageSize()));
        vmImage.setImageFormat(vm.getImageExportInfo().getFormat());
        vmImage.setImageSlimStatus(EnumVmImageSlimStatus.SLIM_SUCCEED);
        vmImage.setStatus(EnumVmImageStatus.PUBLISHED);
        vmImage.setVirtualSize(vmImage.getSystemDiskSize());
        vmImage.setVisibleType("private");
        VMImage res = vmImageService.createVmImageAllInfo(vmImage);
        if (res == null) {
            LOGGER.error("save image info to imageMgmt fail.");
            return false;
        }
        vmAppVmService.updateVmStatus(vmId, EnumVMStatus.EXPORTED, res.getId());
        return true;

    }

    private boolean queryImageInfoFromFileSystem() {
        String vmId = (String) getContext().getParameter(IContextParameter.PARAM_VM_ID);
        ImageExportInfo imageExportInfo = vmAppOperationService.getImageExportInfo(vmId);
        int waitingTime = 0;
        // try to 3 time if return null
        int failNum = 0;
        String url = imageExportInfo.getDownloadUrl();
        while (waitingTime < TIMEOUT) {
            FileSystemResponse imageResult = HttpClientUtil.queryImageCheck(url);
            if (imageResult == null) {
                failNum++;
            } else {
                String checkSum = imageResult.getCheckStatusResponse().getCheckInfo().getChecksum();
                if (!StringUtils.isEmpty(checkSum)) {
                    imageExportInfo.setCheckSum(checkSum);
                    imageExportInfo.setImageFileName(imageResult.getFileName());
                    imageExportInfo.setStatus(EnumImageExportStatus.SUCCESS);
                    imageExportInfo
                        .setFormat(imageResult.getCheckStatusResponse().getCheckInfo().getImageInfo().getFormat());
                    imageExportInfo.setImageSize(
                        imageResult.getCheckStatusResponse().getCheckInfo().getImageInfo().getImageSize());
                    imageExportInfo.setDownloadUrl(url + "/action/download");
                    vmAppOperationService.modifyExportInfo(vmId, imageExportInfo);
                    return true;
                }
            }
            if (failNum >= 3) {
                return false;
            }
            try {
                Thread.sleep(INTERVAL);
                waitingTime += INTERVAL;
            } catch (InterruptedException e) {
                LOGGER.error("export image sleep failed.");
                Thread.currentThread().interrupt();
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }

    private Boolean modifyImageExportInfo(EnumImageExportStatus status, String log) {
        String vmId = (String) getContext().getParameter(IContextParameter.PARAM_VM_ID);
        ImageExportInfo imageExportInfo = vmAppOperationService.getImageExportInfo(vmId);
        imageExportInfo.setStatus(status);
        imageExportInfo.setLog(log);
        vmAppOperationService.modifyExportInfo(vmId, imageExportInfo);
        return true;
    }
}
